#ifndef __BOX__
#define __BOX__

#include "ITexture.h"
#include "..\Math\Point3.h"
#include "..\Math\Point2.h"
#include "..\Math\Colorb.h"

/// <summary>
/// Defines a full 3D box to be rendered.
/// </summary>
struct Box
{
public:
	/// <summary>
	/// The position of this box relative to the position of the block.
	/// </summary>
	Point3i		Position;

	/// <summary>
	/// The bounds of this box.
	/// </summary>
	Point3i		Bounds;

	ITexture	*Texture;
	Point2i		TexCoords[6];
	int			Inset[6];
	Colorb		Colors[6];

	/// <summary>
	/// Initializes a new instance of the <see cref="Box" /> struct.
	/// </summary>
	/// <param name="texture">The texture that this box will use.</param>
	/// <param name="bounds">The bounds of the box.</param>
	/// <param name="pos">The position of the box relative to the position of the block.</param>
	Box(ITexture *texture = nullptr, const Point3i &bounds = Point3i(16, 16, 16), const Point3i &pos = Point3i::Zero) :
		Position(pos),
		Bounds(bounds),
		Texture(texture)
	{
		memset(Inset, 0, sizeof(int) * 6);
		memset(Colors, 255, sizeof(Colorb) * 6);
		memset(TexCoords, 0, sizeof(Point2i) * 6);
	}
	
	/// <summary>
	/// Unwraps the box in a similar format that Minecraft handles box-shaped textures.
	/// This can be used to quickly unwrap models, such as chests.
	/// </summary>
	/// <param name="topLeftOfFront">The top-left of the north face.</param>
	/// <returns>The current box instance.</returns>
	Box &Unwrap(const Point2i &topLeftOfFront)
	{
		TexCoords[1] = topLeftOfFront + Point2i(Bounds.X, 0);
		TexCoords[0] = TexCoords[1] + Point2i(Bounds.Z, 0);
		TexCoords[2] = topLeftOfFront;
		TexCoords[3] = topLeftOfFront - Point2i(Bounds.Z, 0);
		TexCoords[4] = TexCoords[1] - Point2i(0, Bounds.Z);
		TexCoords[5] = topLeftOfFront - Point2i(0, Bounds.Z);

		return *this;
	}

	/// <summary>
	/// Automatically assigns all of the texture coordinates based on
	/// the position given.
	/// </summary>
	/// <param name="coord">The coordinates to base on.</param>
	/// <returns>The current box instance.</returns>
	Box &SetCoordsFromBounds(const Point2i &coord)
	{
		SetTop(coord + Point2i(Position.X, Position.Z));
		SetBottom(coord + Point2i(Position.X, Position.Z));
		SetSouth(coord + Point2i(Position.X, Position.Y));
		SetNorth(coord + Point2i(Position.X, Position.Y));
		SetWest(coord + Point2i(Position.Z, Position.Y));
		SetEast(coord + Point2i(Position.Z, Position.Y));
		return *this;
	}
	
	/// <summary>
	/// Sets the texture coordinates & color for a specific side of the box.
	/// </summary>
	/// <param name="index">The direction to assign.</param>
	/// <param name="texCoord">The texture coord.</param>
	/// <param name="color">The color.</param>
	/// <returns>The current box instance.</returns>
	Box &SetIndex(const int &index, const Point2i &texCoord, const Colorb &color = Colorb::White)
	{
		TexCoords[index] = texCoord;
		Colors[index] = color;

		return *this;
	}
	
	/// <summary>
	/// Sets a range of sides to the specified texture coordinates & color.
	/// </summary>
	/// <param name="start">The start range.</param>
	/// <param name="end">The end range.</param>
	/// <param name="texCoord">The texture coord.</param>
	/// <param name="color">The color.</param>
	/// <returns>The current box instance.</returns>
	Box &SetRange(const int &start, const int &end, const Point2i &texCoord, const Colorb &color = Colorb::White)
	{
		for (int i = start; i < end; ++i)
			SetIndex(i, texCoord, color);

		return *this;
	}

	Box &SetNorth(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetIndex(0, texCoord, color); }
	Box &SetEast(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetIndex(1, texCoord, color); }
	Box &SetSouth(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetIndex(2, texCoord, color); }
	Box &SetWest(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetIndex(3, texCoord, color); }
	Box &SetBottom(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetIndex(4, texCoord, color); }
	Box &SetTop(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetIndex(5, texCoord, color); }

	Box &SetSides(const Point2i &texCoord, const Colorb &color = Colorb::White)
	{
		for (int i = 0; i < 4; ++i)
			SetIndex(i, texCoord, color);

		return *this;
	}

	Box &SetAll(const Point2i &texCoord, const Colorb &color = Colorb::White) { return SetRange(0, 6, texCoord, color); }

	/// <summary>
	/// Sets the inset of a face. This will inset (or outset, if negative) a face by a specific amount of
	/// units (1 unit = 1/16th of a block). 
	/// </summary>
	/// <param name="index">The direction index.</param>
	/// <param name="value">The value.</param>
	/// <returns>The current box instance.</returns>
	Box &SetInset(const int &index, const int &value)
	{
		Inset[index] = value;
		return *this;
	}

	Box &SetNorthInset(const int &value) { return SetInset(0, value); }
	Box &SetEastInset(const int &value) { return SetInset(1, value); }
	Box &SetSouthInset(const int &value) { return SetInset(2, value); }
	Box &SetWestInset(const int &value) { return SetInset(3, value); }
	Box &SetBottomInset(const int &value) { return SetInset(4, value); }
	Box &SetTopInset(const int &value) { return SetInset(5, value); }
	
	Box &SetSidesInset(const int &value)
	{
		for (int i = 0; i < 4; ++i)
			SetInset(i, value);

		return *this;
	}
};

#endif